Die Boost C++ Bibliotheken


Kapitel 11: Serialisierung


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


11.1 Allgemeines

Die Boost C++ Bibliothek Serialization ermöglicht es, Objekte in einem C++-Programm in eine Byte-Sequenz umzuwandeln, diese zu speichern und zu einem späteren Zeitpunkt die gleichen Objekte von dieser Byte-Sequenz wieder zu laden. Dabei stehen verschiedene Datenformate einschließlich XML zur Verfügung, die festlegen, nach welchen Regeln die Byte-Sequenz gebildet wird. Alle von Boost.Serialization unterstützten Formate sind jedoch in gewisser Weise proprietär. So kann zum Beispiel das XML-Format nicht genutzt werden, um Daten mit anderen Anwendungen auszutauschen, die nicht in C++ entwickelt sind und nicht Boost.Serialization verwenden. Denn alle Daten, die im XML-Format gespeichert werden, sind daraufhin ausgerichtet, die gleichen C++-Objekte zu laden, die vorher gespeichert wurden. Das XML-Format hat also lediglich den Vorteil, dass die serialisierten C++-Objekte leichter verständlich sind, was zum Beispiel die Fehlersuche erleichtern kann.


11.2 Archive

Das zentrale Konzept in Boost.Serialization ist das Archiv. Das Archiv stellt die Byte-Sequenz dar, die serialisierte C++-Objekte repräsentiert. Objekte können einem Archiv hinzugefügt und damit serialisiert werden oder von einem Archiv geladen werden. Dies setzt voraus, dass die gleichen Datentypen verwendet werden. Nur dann können beim Laden die gleichen C++-Objekte erstellt werden, die zuvor gespeichert wurden.

Im Folgenden wird Ihnen ein erstes einfaches Beispiel gezeigt.

#include <boost/archive/text_oarchive.hpp> 
#include <iostream> 

int main() 
{ 
  boost::archive::text_oarchive oa(std::cout); 
  int i = 1; 
  oa << i; 
} 

Boost.Serialization stellt mehrere Archiv-Klassen zur Verfügung. So ist in der Headerdatei boost/archive/text_oarchive.hpp das Archiv boost::archive::text_oarchive definiert. Dieses Archiv ermöglicht es, Objekte als Text-Stream zu serialisieren. So gibt obiges Programm 22 serialization::archive 5 1 auf die Standardausgabe aus.

Wie Sie sehen können Sie das Objekt oa vom Typ boost::archive::text_oarchive wie einen Stream verwenden und per << eine Variable serialisieren. Sie sollten Archive aber nicht als herkömmliche Streams ansehen, in die Sie nun beliebig Daten ablegen können. Immerhin sollen die in einem Archiv serialisierten Daten später wieder gelesen werden, was erfordert, das beim Laden exakt die gleichen Datentypen verwendet werden und Daten in der richtigen Reihenfolge gelesen werden. Sehen Sie sich dazu folgendes Beispiel an, in dem die Variable vom Typ int nicht nur serialisiert, sondern auch wieder geladen wird.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <iostream> 
#include <fstream> 

void save() 
{ 
  std::ofstream file("archiv.txt"); 
  boost::archive::text_oarchive oa(file); 
  int i = 1; 
  oa << i; 
} 

void load() 
{ 
  std::ifstream file("archiv.txt"); 
  boost::archive::text_iarchive ia(file); 
  int i = 0; 
  ia >> i; 
  std::cout << i << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Während die Klasse boost::archive::text_oarchive verwendet werden kann, um Daten als Text-Stream zu serialisieren, kann boost::archive::text_iarchive verwendet werden, um Daten von einem derartigen Stream wieder zu lesen. Um diese Klasse zu verwenden, muss die Headerdatei boost/archive/text_iarchive.hpp eingebunden werden.

Wie Sie sehen erwarten Archive im Konstruktor als Parameter einen Input- oder Output-Stream. Archive greifen auf diese Streams zu, um serialisierte Daten dort auszugeben oder von dort zu laden. So wird im obigen Programm auf eine Datei zugegriffen. Sie können jedoch auch zum Beispiel einen Stringstream verwenden.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  int i = 1; 
  oa << i; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  int i = 0; 
  ia >> i; 
  std::cout << i << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Das obige Programm gibt wie das vorherige Programm 1 auf die Standardausgabe aus. Im Gegensatz zum vorherigen Programm serialisiert es Daten jedoch nicht in einer Datei, sondern in einem Stringstream.

Sie wissen nun, wie Variablen primitiver Datentypen serialisiert werden. Im folgenden Programm sehen Sie, wie die Serialisierung von Objekten benutzerdefinierter Datentypen erfolgt.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person p(31); 
  oa << p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person p; 
  ia >> p; 
  std::cout << p.age() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Wenn Objekte benutzerdefinierter Datentypen serialisiert werden sollen, müssen sie eine Methode namens serialize() definieren. Diese Methode wird aufgerufen, wenn ein Objekt serialisiert wird oder von einem Byte-Stream geladen wird. Da serialize() sowohl zum Speichern als auch zum Laden verwendet wird, bietet Boost.Serialization neben << und >> einen Operator an, der automatisch das richtige tut. Wenn Sie den Operator & verwenden, müssen Sie in serialize() nicht zwischen Speichern und Laden unterscheiden.

Die Methode serialize() wird automatisch aufgerufen, wenn ein Objekt serialisiert oder geladen wird. Sie sollte niemals direkt aufgerufen werden und daher privat sein. Damit Boost.Serialization dennoch auf die Methode zugreifen kann, muss die Klasse boost::serialization::access als Freund deklariert werden.

Unter Umständen können Sie eine Klasse nicht ändern und ihr keine Methode serialize() hinzufügen. Dies trifft zum Beispiel auf Klassen aus dem C++ Standard oder anderen Bibliotheken zu.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  friend void serialize(Archive &ar, person &p, const unsigned int version); 

  int age_; 
}; 

template <typename Archive> 
void serialize(Archive &ar, person &p, const unsigned int version) 
{ 
  ar & p.age_; 
} 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person p(31); 
  oa << p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person p; 
  ia >> p; 
  std::cout << p.age() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Wenn Sie einen Datentypen serialisierbar machen wollen, den Sie nicht ändern können, können Sie wie im obigen Programm eine freistehende Funktion serialize() definieren. Als zweiter Parameter muss dieser Funktion eine Referenz auf ein Objekt des entsprechenden Datentypen übergeben werden.

Wenn der Datentyp, den Sie serialisieren wollen, private Eigenschaften besitzt und Sie nicht über öffentliche Methoden auf sie zugreifen können, wird es problematisch. Sie müssen den Datentypen dann unter Umständen dennoch ändern. Denn ohne die friend-Deklaration würde im obigen Programm die Funktion serialize() nicht auf die private Eigenschaft age_ zugreifen können.

Für viele Klassen aus dem C++ Standard stellt Boost.Serialization glücklicherweise entsprechende serialize()-Funktionen zur Verfügung. Es müssen lediglich zusätzliche Headerdateien eingebunden werden, um Objekte zu serialisieren, die auf Klassen aus dem C++ Standard basieren.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/string.hpp> 
#include <iostream> 
#include <sstream> 
#include <string> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age, const std::string &name) 
    : age_(age), name_(name) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

  std::string name() const 
  { 
    return name_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  friend void serialize(Archive &ar, person &p, const unsigned int version); 

  int age_; 
  std::string name_; 
}; 

template <typename Archive> 
void serialize(Archive &ar, person &p, const unsigned int version) 
{ 
  ar & p.age_; 
  ar & p.name_; 
} 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person p(31, "Boris"); 
  oa << p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person p; 
  ia >> p; 
  std::cout << p.age() << std::endl; 
  std::cout << p.name() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Im obigen Programm wird die Klasse person um einen Namen vom Typ std::string erweitert. Damit diese Eigenschaft serialisiert werden kann, muss die Headerdatei boost/serialization/string.hpp eingebunden werden. Diese stellt die für std::string notwendige freistehende Funktion serialize() zur Verfügung.

Wie bereits erwähnt definiert Boost.Serialization für zahlreiche Klassen aus dem C++ Standard serialize()-Funktionen. Diese Funktionen sind in Headerdateien definiert, die den gleichen Namen tragen wie die entsprechenden Headerdateien aus dem C++ Standard. Wenn Sie also wie im obigen Programm Objekte vom Typ std::string serialisieren wollen, müssen Sie die Headerdatei boost/serialization/string.hpp einbinden. Wollen Sie jedoch ein Objekt vom Typ std::vector serialisieren, greifen Sie auf die Headerdatei boost/serialization/vector.hpp zu. Es ist also leicht erkennbar, welche Headerdateien Sie aus Boost.Serialization einbinden müssen.

Ein Parameter von serialize(), der bisher ignoriert wurde, ist version. Dieser Parameter ist dann von Bedeutung, wenn Sie davon ausgehen, dass Sie Ihr Programm im Laufe der Zeit weiterentwickeln, Ihre Archive aber aufwärtskompatibel sein sollen. So wird im nächsten Beispiel davon ausgegangen, dass Archive mit der Klasse person aufwärtskompatibel sein sollen. Während die ursprüngliche Version von person keinen Namen enthielt und somit auch kein Name im Archiv gespeichert wurde, soll die neue Version der Klasse person dennoch mit alten Archiven umgehen können.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/string.hpp> 
#include <iostream> 
#include <sstream> 
#include <string> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age, const std::string &name) 
    : age_(age), name_(name) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

  std::string name() const 
  { 
    return name_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  friend void serialize(Archive &ar, person &p, const unsigned int version); 

  int age_; 
  std::string name_; 
}; 

template <typename Archive> 
void serialize(Archive &ar, person &p, const unsigned int version) 
{ 
  ar & p.age_; 
  if (version > 0) 
    ar & p.name_; 
} 

BOOST_CLASS_VERSION(person, 1) 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person p(31, "Boris"); 
  oa << p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person p; 
  ia >> p; 
  std::cout << p.age() << std::endl; 
  std::cout << p.name() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Das Makro BOOST_CLASS_VERSION wird verwendet, um die Versionsnummer für eine Klasse zu setzen. So wird im obigen Programm die Versionsnummer für die Klasse person auf 1 gesetzt. Standardmäßig - wenn BOOST_CLASS_VERSION nicht verwendet wird - ist die Versionnummer 0.

Die Versionsnummer wird immer im Archiv gespeichert und ist somit Bestandteil jedes Archivs. Während beim Speichern die Versionsnummer verwendet wird, die mit BOOST_CLASS_VERSION für die entsprechende Klasse angegeben ist, wird beim Laden der Parameter version der Funktion serialize() auf den Wert gesetzt, der im Archiv gespeichert ist. Wenn also die neue Version der Klasse person auf ein Archiv zugreifen würde, in dem ein Objekt basierend auf der alten Version dieser Klasse gespeichert ist, würde nicht versucht werden, einen Namen zu lesen. Denn die alte Version der Klasse person besitzt keinen Namen. Über diesen Mechanismus ermöglicht Boost.Serialization aufwärtskompatible Archive.


11.3 Zeiger und Referenzen

Boost.Serialization kann Zeiger und Referenzen serialisieren. Weil ein Zeiger eine Adresse eines Objekts speichert, macht allein die Serialisierung dieser Adresse nicht viel Sinn. Wenn ein Zeiger oder eine Referenz serialisiert werden, wird deswegen automatisch das Objekt serialisiert, auf das der Zeiger oder die Referenz verweisen.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person *p = new person(31); 
  oa << p; 
  std::cout << std::hex << p << std::endl; 
  delete p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person *p; 
  ia >> p; 
  std::cout << std::hex << p << std::endl; 
  std::cout << p->age() << std::endl; 
  delete p; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Im obigen Programm wird mit new ein neues Objekt vom Typ person erstellt und im Zeiger p verankert. Dieser Zeiger - und nicht *p - wird dann serialisiert. Dabei speichert Boost.Serialization nun nicht nur die Adresse des Objekts, die im Zeiger gespeichert ist. Es wird automatisch das Objekt serialisiert, auf das p zeigt.

Beim Laden des Archivs wird p nicht notwendigerweise auf die gleiche Adresse gesetzt. Stattdessen wird automatisch ein neues Objekt erstellt, dessen Adresse in p gespeichert wird. Boost.Serialization garantiert also, dass das Objekt identisch ist mit dem, das zuvor serialisiert wurde. Für die Adresse gilt dies nicht.

Da im Zusammenhang mit dynamisch reserviertem Speicher in modernem C++-Code smart pointers verwendet werden, bietet Boost.Serialization auch für diese eine entsprechende Unterstützung an.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/scoped_ptr.hpp> 
#include <boost/scoped_ptr.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  boost::scoped_ptr<person> p(new person(31)); 
  oa << p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  boost::scoped_ptr<person> p; 
  ia >> p; 
  std::cout << p->age() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Im obigen Programm wird der smart pointer boost::scoped_ptr verwendet, um ein dynamisch reserviertes Objekt vom Typ person zu verwalten. Damit ein Zeiger vom Typ boost::scoped_ptr serialisiert werden kann, muss jedoch die Headerdatei boost/serialization/scoped_ptr.hpp eingebunden werden.

Für den Fall, dass Sie den smart pointer boost::shared_ptr serialisieren möchten, müssen Sie die Headerdatei boost/serialization/shared_ptr.hpp verwenden.

Das folgende Programm verwendet nun anstatt eines Zeigers eine Referenz.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person p(31); 
  person &pp = p; 
  oa << pp; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person p; 
  person &pp = p; 
  ia >> pp; 
  std::cout << pp.age() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Wie Sie sehen kann Boost.Serialization auch problemlos Referenzen serialisieren. Ähnlich wie bei Zeigern wird auch hier automatisch das referenzierte Objekt serialisiert.


11.4 Serialisieren von Objekten aus Klassenhierarchien

Wenn Objekte serialisiert werden sollen, die auf Typen aus Klassenhierarchien basieren, muss in Kindklassen innerhalb der Methode serialize() auf eine Funktion boost::serialization::base_object() zugegriffen werden. Nur diese Funktion stellt sicher, dass von Elternklassen geerbte Eigenschaften einwandfrei serialisiert werden. Sehen Sie sich dazu das folgende Programm an, das um eine Klasse developer erweitert wurde, die von person abgeleitet ist.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/string.hpp> 
#include <iostream> 
#include <sstream> 
#include <string> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

class developer 
  : public person 
{ 
public: 
  developer() 
  { 
  } 

  developer(int age, const std::string &language) 
    : person(age), language_(language) 
  { 
  } 

  std::string language() const 
  { 
    return language_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & boost::serialization::base_object<person>(*this); 
    ar & language_; 
  } 

  std::string language_; 
}; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  developer d(31, "C++"); 
  oa << d; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  developer d; 
  ia >> d; 
  std::cout << d.age() << std::endl; 
  std::cout << d.language() << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Beide Klasse - sowohl person als auch developer - besitzen eine private Methode serialize(). Demnach können Objekte, die auf einer dieser Klassen basieren, serialisiert werden. Weil developer jedoch von person abgeleitet ist, muss die Methode serialize() in dieser Klasse dafür sorgen, dass die von person geerbten Eigenschaften ebenfalls serialisiert werden.

Geerbte Eigenschaften werden serialisiert, indem in der Kindklasse in serialize() mit boost::serialization::base_object() auf die Elternklasse zugegriffen wird. Es ist zwingend notwendig, diese Funktion und nicht zum Beispiel static_cast zu verwenden, weil nur boost::serialization::base_object() eine einwandfreie Serialisierung sicherstellt.

Wenn Objekte dynamisch erzeugt werden, können ihre Adressen in Zeigern vom Typ der Elternklasse gespeichert werden. Dass Boost.Serialization auch in diesem Fall Objekte korrekt serialisieren kann, zeigt das folgende Programm.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/string.hpp> 
#include <boost/serialization/export.hpp> 
#include <iostream> 
#include <sstream> 
#include <string> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  virtual int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

class developer 
  : public person 
{ 
public: 
  developer() 
  { 
  } 

  developer(int age, const std::string &language) 
    : person(age), language_(language) 
  { 
  } 

  std::string language() const 
  { 
    return language_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & boost::serialization::base_object<person>(*this); 
    ar & language_; 
  } 

  std::string language_; 
}; 

BOOST_CLASS_EXPORT(developer) 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  person *p = new developer(31, "C++"); 
  oa << p; 
  delete p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  person *p; 
  ia >> p; 
  std::cout << p->age() << std::endl; 
  delete p; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Im obigen Programm wird in der Funktion save() ein Objekt vom Typ developer erstellt und in einem Zeiger vom Typ person* verankert. Dieser Zeiger wird dann per << an das Archiv übergeben.

Wie bereits im vorherigen Abschnitt gesehen wird in diesem Fall automatisch das Objekt serialisiert, auf das der Zeiger verweist. Damit Boost.Serialization erkennt, dass ein Objekt vom Typ developer serialisiert werden muss, obwohl der Zeiger den Typ person* hat, muss die Klasse developer der Bibliothek bekannt gemacht werden. Dies erfolgt mit Hilfe des Makros BOOST_CLASS_EXPORT, das in der Headerdatei boost/serialization/export.hpp definiert ist. Ohne dieses Makro könnte Boost.Serialization das Objekt vom Typ developer nicht richtig serialisieren, weil der Typ developer nicht in der Zeigerdefinition auftaucht und daher für Boost.Serialization unbekannt wäre.

Sie müssen das Makro BOOST_CLASS_EXPORT immer dann verwenden, wenn Sie Kindklassen erstellen und Objekte vom Typ dieser Kindklassen serialisieren wollen, dies aber über einen Zeiger vom Typ einer Elternklasse tun.

Ein Nachteil des Makros BOOST_CLASS_EXPORT ist, dass aufgrund der statischen Registrierung auch Klassen registiert werden, die wohlmöglich in einem Programm gar nicht zur Serialisierung verwendet werden. Boost.Serialization bietet auch für diesen Fall ein Lösung an.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/string.hpp> 
#include <boost/serialization/export.hpp> 
#include <iostream> 
#include <sstream> 
#include <string> 

std::stringstream ss; 

class person 
{ 
public: 
  person() 
  { 
  } 

  person(int age) 
    : age_(age) 
  { 
  } 

  virtual int age() const 
  { 
    return age_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & age_; 
  } 

  int age_; 
}; 

class developer 
  : public person 
{ 
public: 
  developer() 
  { 
  } 

  developer(int age, const std::string &language) 
    : person(age), language_(language) 
  { 
  } 

  std::string language() const 
  { 
    return language_; 
  } 

private: 
  friend class boost::serialization::access; 

  template <typename Archive> 
  void serialize(Archive &ar, const unsigned int version) 
  { 
    ar & boost::serialization::base_object<person>(*this); 
    ar & language_; 
  } 

  std::string language_; 
}; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  oa.register_type<developer>(); 
  person *p = new developer(31, "C++"); 
  oa << p; 
  delete p; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  ia.register_type<developer>(); 
  person *p; 
  ia >> p; 
  std::cout << p->age() << std::endl; 
  delete p; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Anstatt das Makro BOOST_CLASS_EXPORT zu verwenden wird im obigen Programm für ein Archiv die Template-Methode register_type() aufgerufen. Ihr wird der entsprechende Typ, der registriert werden soll, als Template-Paramter übergeben.

Beachten Sie dabei, dass register_type() sowohl in save() als auch in load() aufgerufen werden muss.

Der Vorteil der dynamischen Registrierung mit register_type() ist, dass nur die Klassen registriert werden müssen, die im jeweiligen Programm für die Serialisierung verwendet werden sollen. Wenn Sie zum Beispiel eine Bibliothek erstellen, wissen Sie nicht im Vorhinein, ob und welche Klassen Ihrer Bibliothek ein Entwickler später für die Serialisierung verwenden möchte. Das Makro BOOST_CLASS_EXPORT macht es zwar recht einfach. Nur werden auf diese Weise wohlmöglich Typen registriert, die von einem Entwickler später unter Umständen gar nicht für die Serialisierung verwendet werden.


11.5 Wrapper-Funktionen zur Optimierung

Während Sie Boost.Serialization nun soweit kennengelernt haben, dass Sie Objekte serialisieren können, werden Ihnen abschließend Wrapper-Funktionen vorgestellt, mit denen Sie die Serialisierung optimieren können. Indem Sie Wrapper-Funktionen verwenden, markieren Sie gewissermaßen Objekte, was Boost.Serialization ermöglicht, entsprechende Optimierungstechniken anzuwenden.

Im Folgenden sehen Sie ein Programm, das Boost.Serialization ohne eine Wrapper-Funktion verwendet.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/array.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  boost::array<int, 3> a = { 0, 1, 2 }; 
  oa << a; 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  boost::array<int, 3> a; 
  ia >> a; 
  std::cout << a[0] << ", " << a[1] << ", " << a[2] << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Der Text-Stream, der im obigen Programm bei der Serialisierung erstellt und auf die Standardausgabe ausgegeben wird, ist 22 serialization::archive 5 0 0 3 0 1 2. Mit Hilfe der Wrapper-Funktion boost::serialization::make_array() kann der Text-Stream auf 22 serialization::archive 5 0 1 2 verkürzt werden.

#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp> 
#include <boost/serialization/array.hpp> 
#include <boost/array.hpp> 
#include <iostream> 
#include <sstream> 

std::stringstream ss; 

void save() 
{ 
  boost::archive::text_oarchive oa(ss); 
  boost::array<int, 3> a = { 0, 1, 2 }; 
  oa << boost::serialization::make_array(a.data(), a.size()); 
} 

void load() 
{ 
  boost::archive::text_iarchive ia(ss); 
  boost::array<int, 3> a; 
  ia >> boost::serialization::make_array(a.data(), a.size()); 
  std::cout << a[0] << ", " << a[1] << ", " << a[2] << std::endl; 
} 

int main() 
{ 
  save(); 
  load(); 
} 

Der Funktion boost::serialization::make_array() muss die Adresse eines Arrays und dessen Länge übergeben werden. Weil die Länge somit hardcodiert ist, muss sie nicht als Bestandteil des Objekts vom Typ boost::array serialisiert werden. Die Funktion boost::serialization::make_array() bietet sich also immer dann an, wenn eine Klasse wie boost::array oder auch std::vector ein Array enthalten und das Array direkt serialisiert werden kann. Andere Eigenschaften, die wohlmöglich zur Serialisierung der entsprechenden Klasse gespeichert werden müssten, werden nicht serialisiert.

Eine weitere Wrapper-Funktion, die Boost.Serialization anbietet, ist boost::serialization::make_binary_object(). Ihr muss ähnlich wie boost::serialization::make_array() eine Adresse und eine Länge übergeben werden. Der Unterschied zwischen den beiden Funktionen ist, dass boost::serialization::make_binary_object() ausschließlich für Binärdaten verwendet wird, denen keinerlei Struktur zugrunde liegt, während boost::serialization::make_array() für Arrays verwendet wird.


11.6 Aufgaben

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Entwickeln Sie ein Programm, das beliebig viele Datensätze bestehend aus Name eines Mitarbeiters, der Abteilung, in der er beschäftigt ist, und einer eindeutigen Identifikationsnummer sowohl in einer Datei speichern als auch wieder von einer Datei laden kann. Verwenden Sie ein paar Beispieldatensätze, um Ihr Programm zu testen. Zeigen Sie nach einem Ladevorgang die Datensätze auf dem Bildschirm an.

  2. Erweitern Sie Ihr Programm aus Aufgabe 1, indem Sie zusätzlich den Geburtstag für jeden Mitarbeiter speichern. Ihr Programm soll dennoch Dateien laden können, die mit Ihrer Lösung für Aufgabe 1 erstellt wurden.